package com.clp.protocol.modbus_tcp.client.master_state;

import com.clp.protocol.core.pdu.nbytepdu.FailedToSendFrameException;
import com.clp.protocol.modbus_tcp.client.MasterImpl;
import com.clp.protocol.modbus_tcp.client.async.SendMasterPromise;
import com.clp.protocol.modbus_tcp.definition.ErrCode;
import com.clp.protocol.modbus_tcp.mb_frame.ReqMbFrm;
import com.clp.protocol.modbus_tcp.mb_frame.RespErrMbFrm;
import com.clp.protocol.modbus_tcp.mb_frame.RespMbFrm;
import com.clp.protocol.modbus_tcp.mb_frame.RespOkMbFrm;
import com.clp.protocol.modbus_tcp.mb_frame.mb_body.RespErrMbBody;
import com.clp.protocol.modbus_tcp.mb_frame.mb_body.RespOkMbBody;
import com.clp.protocol.modbus_tcp.mb_frame.mb_body.mb_data.RespOkMbData;
import com.clp.protocol.modbus_tcp.mb_frame.mb_body.mb_data.resp_ok.*;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SendRecvMasterState extends MasterState {

    @Override
    public Type type() {
        return Type.SEND_RECV;
    }

    private enum State {
        /**
         * 请求状态
         */
        REQ,
        /**
         * 正确响应状态
         */
        RESP_OK,
        /**
         * 错误响应状态
         */
        RESP_ERR
    }

    @Setter
    @Accessors(chain = true)
    private static class SendState {
        private final int sendSeq;
        private volatile State state;
        private Date lastSendReqTime;

        public SendState(int sendSeq) {
            this.sendSeq = sendSeq;
            this.reset();
        }

        private void reset() {
            this.state = State.RESP_OK;
            this.lastSendReqTime = new Date();
        }
    }

    private final ConcurrentMap<Integer, SendState> sendStateMap;
    private final PromiseMap<SendMasterPromise<ForceMultiCoilsRespOkMbData>> forceMultiCoilsPromiseMap;
    private final PromiseMap<SendMasterPromise<ForceMultiRegsRespOkMbData>> forceMultiRegsPromiseMap;
    private final PromiseMap<SendMasterPromise<ForceSingleCoilRespOkMbData>> forceSingleCoilPromiseMap;
    private final PromiseMap<SendMasterPromise<PresetSingleRegRespOkMbData>> presetSingleRegPromiseMap;
    private final PromiseMap<SendMasterPromise<ReadCoilStatusRespOkMbData>> readCoilStatusPromiseMap;
    private final PromiseMap<SendMasterPromise<ReadHoldRegsRespOkMbData>> readHoldRegsPromiseMap;
    private final PromiseMap<SendMasterPromise<ReadInputRegsRespOkMbData>> readInputRegsPromiseMap;
    private final PromiseMap<SendMasterPromise<ReadInputStatusRespOkMbData>> readInputStatusPromiseMap;

    public SendRecvMasterState(MasterImpl masterImpl) {
        super(masterImpl);
        this.sendStateMap = new ConcurrentHashMap<>();
        this.forceMultiCoilsPromiseMap = new PromiseMap<>();
        this.forceMultiRegsPromiseMap = new PromiseMap<>();
        this.forceSingleCoilPromiseMap = new PromiseMap<>();
        this.presetSingleRegPromiseMap = new PromiseMap<>();
        this.readCoilStatusPromiseMap = new PromiseMap<>();
        this.readHoldRegsPromiseMap = new PromiseMap<>();
        this.readInputRegsPromiseMap = new PromiseMap<>();
        this.readInputStatusPromiseMap = new PromiseMap<>();
    }

    @Override
    public void reset() {
        this.sendStateMap.clear();
    }

    @Override
    public ReqMbFrm updateBySending(ReqMbFrm reqMbFrm) {
        if (reqMbFrm == null) return null;

        int sendSeq = reqMbFrm.getHead().getSeq();
        SendState sendState = sendStateMap.get(sendSeq);

        // 发送失败的情况
        if (sendState != null) {
            log.error("上次同序号请求还未结束(序号：{})，放弃本次请求！", sendSeq);
            switch (reqMbFrm.getBody().getFuncCode()) {
                case READ_COIL_STATUS:
                    readCoilStatusPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setSendSuccess(false).setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                        promise.setFailure(new FailedToSendFrameException(reqMbFrm));
                        return true;
                    });
                    break;
                case FORCE_MULTIPLE_REGISTERS:
                    forceMultiRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setSendSuccess(false).setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                        promise.setFailure(new FailedToSendFrameException(reqMbFrm));
                        return true;
                    });
                    break;
                case READ_HOLDING_REGISTERS:
                    readHoldRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setSendSuccess(false).setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                        promise.setFailure(new FailedToSendFrameException(reqMbFrm));
                        return true;
                    });
                    break;
                case PRESET_SINGLE_REGISTER:
                    presetSingleRegPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setSendSuccess(false).setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                        promise.setFailure(new FailedToSendFrameException(reqMbFrm));
                        return true;
                    });
                    break;
                case READ_INPUT_REGISTERS:
                    readInputRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setSendSuccess(false).setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                        promise.setFailure(new FailedToSendFrameException(reqMbFrm));
                        return true;
                    });
                    break;
                case FORCE_MULTIPLE_COILS:
                    forceMultiCoilsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setSendSuccess(false).setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                        promise.setFailure(new FailedToSendFrameException(reqMbFrm));
                        return true;
                    });
                    break;
                case WRITE_FILE_RECORD:
                    break;
                case READ_INPUT_STATUS:
                    readInputStatusPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setSendSuccess(false).setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                        promise.setFailure(new FailedToSendFrameException(reqMbFrm));
                        return true;
                    });
                    break;
                case FORCE_SINGLE_COIL:
                    forceSingleCoilPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setSendSuccess(false).setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                        promise.setFailure(new FailedToSendFrameException(reqMbFrm));
                        return true;
                    });
                    break;
                case READ_FILE_RECORD:
                    break;
                case READ_FIFO_QUEUE:
                    break;
            }
            return null;
        }

        // 设置发送成功
        switch (reqMbFrm.getBody().getFuncCode()) {
            case READ_COIL_STATUS:
                readCoilStatusPromiseMap.removeIf(sendSeq, promise -> {
                    promise.getRes().setSendSuccess(true);
                    return false;
                });
                break;
            case FORCE_MULTIPLE_REGISTERS:
                forceMultiRegsPromiseMap.removeIf(sendSeq, promise -> {
                    promise.getRes().setSendSuccess(true);
                    return false;
                });
                break;
            case READ_HOLDING_REGISTERS:
                readHoldRegsPromiseMap.removeIf(sendSeq, promise -> {
                    promise.getRes().setSendSuccess(true);
                    return false;
                });
                break;
            case PRESET_SINGLE_REGISTER:
                presetSingleRegPromiseMap.removeIf(sendSeq, promise -> {
                    promise.getRes().setSendSuccess(true);
                    return false;
                });
                break;
            case READ_INPUT_REGISTERS:
                readInputRegsPromiseMap.removeIf(sendSeq, promise -> {
                    promise.getRes().setSendSuccess(true);
                    return false;
                });
                break;
            case FORCE_MULTIPLE_COILS:
                forceMultiCoilsPromiseMap.removeIf(sendSeq, promise -> {
                    promise.getRes().setSendSuccess(true);
                    return false;
                });
                break;
            case WRITE_FILE_RECORD:
                break;
            case READ_INPUT_STATUS:
                readInputStatusPromiseMap.removeIf(sendSeq, promise -> {
                    promise.getRes().setSendSuccess(true);
                    return false;
                });
                break;
            case FORCE_SINGLE_COIL:
                forceSingleCoilPromiseMap.removeIf(sendSeq, promise -> {
                    promise.getRes().setSendSuccess(true);
                    return false;
                });
                break;
            case READ_FILE_RECORD:
                break;
            case READ_FIFO_QUEUE:
                break;
        }

        sendStateMap.put(sendSeq, new SendState(sendSeq).setState(State.REQ).setLastSendReqTime(new Date()));
        return reqMbFrm;
    }

    @Override
    public RespMbFrm updateByRecving(RespMbFrm respMbFrm) {
        if (respMbFrm == null) return null;

        int sendSeq = respMbFrm.getHead().getSeq();
        SendState sendState = sendStateMap.get(sendSeq);
        if (sendState == null) {
            log.warn("接收到无效的响应报文！(序号：{})", sendSeq);
            return respMbFrm;
        }

        if (respMbFrm.isRespOKType()) {
            RespOkMbFrm respOkMbFrm = respMbFrm.castToRespOkType();
            RespOkMbBody respOkMbBody = respOkMbFrm.getBody();
            switch (respOkMbBody.getFuncCode()) {
                case READ_FIFO_QUEUE:
                    break;
                case READ_FILE_RECORD:
                    break;
                case FORCE_SINGLE_COIL:
                    forceSingleCoilPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(true).setRecvedRespErr(false)
                                .setRespData(respOkMbBody.getData().castTo(ForceSingleCoilRespOkMbData.class));
                        promise.setSuccess();
                        return true;
                    });
                    break;
                case READ_INPUT_STATUS:
                    readInputStatusPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(true).setRecvedRespErr(false)
                                .setRespData(respOkMbBody.getData().castTo(ReadInputStatusRespOkMbData.class));
                        promise.setSuccess();
                        return true;
                    });
                    break;
                case WRITE_FILE_RECORD:
                    break;
                case FORCE_MULTIPLE_COILS:
                    forceMultiCoilsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(true).setRecvedRespErr(false)
                                .setRespData(respOkMbBody.getData().castTo(ForceMultiCoilsRespOkMbData.class));
                        promise.setSuccess();
                        return true;
                    });
                    break;
                case READ_INPUT_REGISTERS:
                    readInputRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(true).setRecvedRespErr(false)
                                .setRespData(respOkMbBody.getData().castTo(ReadInputRegsRespOkMbData.class));
                        promise.setSuccess();
                        return true;
                    });
                    break;
                case PRESET_SINGLE_REGISTER:
                    presetSingleRegPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(true).setRecvedRespErr(false)
                                .setRespData(respOkMbBody.getData().castTo(PresetSingleRegRespOkMbData.class));
                        promise.setSuccess();
                        return true;
                    });
                    break;
                case READ_HOLDING_REGISTERS:
                    readHoldRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(true).setRecvedRespErr(false)
                                .setRespData(respOkMbBody.getData().castTo(ReadHoldRegsRespOkMbData.class));
                        promise.setSuccess();
                        return true;
                    });
                    break;
                case FORCE_MULTIPLE_REGISTERS:
                    forceMultiRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(true).setRecvedRespErr(false)
                                .setRespData(respOkMbBody.getData().castTo(ForceMultiRegsRespOkMbData.class));
                        promise.setSuccess();
                        return true;
                    });
                    break;
                case READ_COIL_STATUS:
                    readCoilStatusPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(true).setRecvedRespErr(false)
                                .setRespData(respOkMbBody.getData().castTo(ReadCoilStatusRespOkMbData.class));
                        promise.setSuccess();
                        return true;
                    });
                    break;
            }
            sendState.setState(State.RESP_OK);
        } else if (respMbFrm.isRespErrType()) {
            RespErrMbFrm respErrMbFrm = respMbFrm.castToRespErrType();
            RespErrMbBody respErrMbBody = respErrMbFrm.getBody();
            ErrCode errCode = respErrMbBody.getData().getErrCode();
            switch (respErrMbBody.getFuncCode()) {
                case READ_FIFO_QUEUE:
                    break;
                case READ_FILE_RECORD:
                    break;
                case FORCE_SINGLE_COIL:
                    forceSingleCoilPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(false).setRecvedRespErr(true)
                                .setRespErrCode(errCode);
                        promise.setFailure(new RuntimeException("接收到错误响应帧！错误码：" + errCode));
                        return true;
                    });
                    break;
                case READ_INPUT_STATUS:
                    readInputStatusPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(false).setRecvedRespErr(true)
                                .setRespErrCode(errCode);
                        promise.setFailure(new RuntimeException("接收到错误响应帧！错误码：" + errCode));
                        return true;
                    });
                    break;
                case WRITE_FILE_RECORD:
                    break;
                case FORCE_MULTIPLE_COILS:
                    forceMultiCoilsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(false).setRecvedRespErr(true)
                                .setRespErrCode(errCode);
                        promise.setFailure(new RuntimeException("接收到错误响应帧！错误码：" + errCode));
                        return true;
                    });
                    break;
                case READ_INPUT_REGISTERS:
                    readInputRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(false).setRecvedRespErr(true)
                                .setRespErrCode(errCode);
                        promise.setFailure(new RuntimeException("接收到错误响应帧！错误码：" + errCode));
                        return true;
                    });
                    break;
                case PRESET_SINGLE_REGISTER:
                    presetSingleRegPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(false).setRecvedRespErr(true)
                                .setRespErrCode(errCode);
                        promise.setFailure(new RuntimeException("接收到错误响应帧！错误码：" + errCode));
                        return true;
                    });
                    break;
                case READ_HOLDING_REGISTERS:
                    readHoldRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(false).setRecvedRespErr(true)
                                .setRespErrCode(errCode);
                        promise.setFailure(new RuntimeException("接收到错误响应帧！错误码：" + errCode));
                        return true;
                    });
                    break;
                case FORCE_MULTIPLE_REGISTERS:
                    forceMultiRegsPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(false).setRecvedRespErr(true)
                                .setRespErrCode(errCode);
                        promise.setFailure(new RuntimeException("接收到错误响应帧！错误码：" + errCode));
                        return true;
                    });
                    break;
                case READ_COIL_STATUS:
                    readCoilStatusPromiseMap.removeIf(sendSeq, promise -> {
                        promise.getRes().setRecvedResp(true).setRecvedRespOk(false).setRecvedRespErr(true)
                                .setRespErrCode(errCode);
                        promise.setFailure(new RuntimeException("接收到错误响应帧！错误码：" + errCode));
                        return true;
                    });
                    break;
            }
            sendState.setState(State.RESP_ERR);
        }

        // 移除
        sendStateMap.remove(sendSeq);
        log.info("移除过程(序号：{})", sendSeq);
        return respMbFrm;
    }

    @Override
    public void startMonitorTasks() {
        tryStopMonitorTasks();
        scheduledFuture = scheduleAtFixedRate(() -> {
            for (Iterator<Map.Entry<Integer, SendState>> iterator = sendStateMap.entrySet().iterator(); iterator.hasNext();) {
                Map.Entry<Integer, SendState> entry = iterator.next();
                SendState sendState = entry.getValue();

                switch (sendState.state) {
                    case REQ:
                        if (new Date().getTime() - sendState.lastSendReqTime.getTime() > 1000 * 3) {

                            forceMultiCoilsPromiseMap.removeIf(sendState.sendSeq, promise -> {
                                promise.getRes().setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                                promise.setFailure(new RuntimeException("请求响应超时！"));
                                return true;
                            });
                            forceMultiRegsPromiseMap.removeIf(sendState.sendSeq, promise -> {
                                promise.getRes().setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                                promise.setFailure(new RuntimeException("请求响应超时！"));
                                return true;
                            });
                            forceSingleCoilPromiseMap.removeIf(sendState.sendSeq, promise -> {
                                promise.getRes().setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                                promise.setFailure(new RuntimeException("请求响应超时！"));
                                return true;
                            });
                            presetSingleRegPromiseMap.removeIf(sendState.sendSeq, promise -> {
                                promise.getRes().setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                                promise.setFailure(new RuntimeException("请求响应超时！"));
                                return true;
                            });
                            readCoilStatusPromiseMap.removeIf(sendState.sendSeq, promise -> {
                                promise.getRes().setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                                promise.setFailure(new RuntimeException("请求响应超时！"));
                                return true;
                            });
                            readHoldRegsPromiseMap.removeIf(sendState.sendSeq, promise -> {
                                promise.getRes().setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                                promise.setFailure(new RuntimeException("请求响应超时！"));
                                return true;
                            });
                            readInputRegsPromiseMap.removeIf(sendState.sendSeq, promise -> {
                                promise.getRes().setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                                promise.setFailure(new RuntimeException("请求响应超时！"));
                                return true;
                            });
                            readInputStatusPromiseMap.removeIf(sendState.sendSeq, promise -> {
                                promise.getRes().setRecvedResp(false).setRecvedRespOk(false).setRecvedRespErr(false);
                                promise.setFailure(new RuntimeException("请求响应超时！"));
                                return true;
                            });

                            log.warn("发送请求3秒内未收到响应，本次请求失败！(序号：{})", sendState.sendSeq);
                        }
                        break;
                    case RESP_OK:
                        break;
                    case RESP_ERR:
                        break;
                }
            }
        }, 0, 1, TimeUnit.SECONDS);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <V extends RespOkMbData> SendMasterPromise<V> register(SendMasterPromise<V> sendPromise) {
        if (sendPromise == null || sendPromise.getRes() == null) return sendPromise;
        if (ForceMultiCoilsRespOkMbData.class.isAssignableFrom(sendPromise.getDClass())) {
            forceMultiCoilsPromiseMap.put(sendPromise.getSendSeq(), ((SendMasterPromise<ForceMultiCoilsRespOkMbData>) sendPromise));
            return null;
        }
        if (ForceMultiRegsRespOkMbData.class.isAssignableFrom(sendPromise.getDClass())) {
            forceMultiRegsPromiseMap.put(sendPromise.getSendSeq(), ((SendMasterPromise<ForceMultiRegsRespOkMbData>) sendPromise));
            return null;
        }
        if (ForceSingleCoilRespOkMbData.class.isAssignableFrom(sendPromise.getDClass())) {
            forceSingleCoilPromiseMap.put(sendPromise.getSendSeq(), ((SendMasterPromise<ForceSingleCoilRespOkMbData>) sendPromise));
            return null;
        }
        if (PresetSingleRegRespOkMbData.class.isAssignableFrom(sendPromise.getDClass())) {
            presetSingleRegPromiseMap.put(sendPromise.getSendSeq(), ((SendMasterPromise<PresetSingleRegRespOkMbData>) sendPromise));
            return null;
        }
        if (ReadCoilStatusRespOkMbData.class.isAssignableFrom(sendPromise.getDClass())) {
            readCoilStatusPromiseMap.put(sendPromise.getSendSeq(), ((SendMasterPromise<ReadCoilStatusRespOkMbData>) sendPromise));
            return null;
        }
        if (ReadHoldRegsRespOkMbData.class.isAssignableFrom(sendPromise.getDClass())) {
            readHoldRegsPromiseMap.put(sendPromise.getSendSeq(), ((SendMasterPromise<ReadHoldRegsRespOkMbData>) sendPromise));
            return null;
        }
        if (ReadInputRegsRespOkMbData.class.isAssignableFrom(sendPromise.getDClass())) {
            readInputRegsPromiseMap.put(sendPromise.getSendSeq(), ((SendMasterPromise<ReadInputRegsRespOkMbData>) sendPromise));
            return null;
        }
        if (ReadInputStatusRespOkMbData.class.isAssignableFrom(sendPromise.getDClass())) {
            readInputStatusPromiseMap.put(sendPromise.getSendSeq(), ((SendMasterPromise<ReadInputStatusRespOkMbData>) sendPromise));
            return null;
        }
        return sendPromise;
    }
}
